local funcs = nil 
local ORIG_FRICTION = 62914

//wall driving, started by lighto and finished and touched up a lot by Haya, he did a excellent job on it
local function managewallmode_post(p)
	// for wallride
	p.prevdrift = p.kartstuff[k_drift]
	p.prevdriftcharge = p.kartstuff[k_driftcharge]
	p.prevdriftboost = p.kartstuff[k_driftboost]
	p.prevdriftend = p.kartstuff[k_driftend]
	p.prevjmp = p.kartstuff[k_jmp]
		
	// evil wall logic: TWO!
	if p.mo.HP_wallmode then
		// Drifting sound
		// Start looping the sound now.
		if (leveltime % 50 == 0 and p.kartstuff[k_drift] != 0) then
			S_StartSound(p.mo, sfx_drift)
		// Leveltime being 50 might take a while at times. We'll start it up once, isntantly.
		elseif ((not S_SoundPlaying(p.mo, sfx_drift)) and p.kartstuff[k_drift] != 0) then
			S_StartSound(p.mo, sfx_drift)
		// Ok, we'll stop now.
		elseif (p.kartstuff[k_drift] == 0) then
			S_StopSoundByID(p.mo, sfx_drift)
		end
		// shit!!!!!
		funcs.kansaiDurifto(p)
		// restore last drift stuff
		if not p.wallspeedlastdriftapplied then
			p.kartstuff[k_drift] = p.prevdrift
			p.kartstuff[k_driftcharge] = p.prevdriftcharge
			p.kartstuff[k_driftboost] = p.prevdriftboost
			p.kartstuff[k_jmp] = p.prevjmp
			p.kartstuff[k_driftend] = p.prevdriftend
			p.wallspeedlastdriftapplied = true
		end
		-- Drifting Left - S_KART_DRIFT1_L
		if p.kartstuff[k_drift] > 0 then
			if not (p.mo.state >= #states[S_KART_DRIFT1_L] and p.mo.state <= #states[S_KART_DRIFT2_L]) then
				p.mo.state = S_KART_DRIFT1_L
			end
		-- Drifting Right - S_KART_DRIFT1_R
		elseif p.kartstuff[k_drift] < 0 then
			if not (p.mo.state >= #states[S_KART_DRIFT1_R] and p.mo.state <= #states[S_KART_DRIFT2_R]) then
				p.mo.state = S_KART_DRIFT1_R
			end
		end
		if p.wallspeed > 4*FRACUNIT then
			// lmaower
			local turnval = FixedAngle(FixedDiv(funcs.getTurnValue(p, p.cmd.driftturn)<<16, 200<<16))
			//print(turnval)
			p.wallangle = $ + turnval
		end
		// here comes the value calculations
		// thanks lighto for doing this for me 
		local rwall = p.mo.HP_curline
		local wang = rwall.angle
		p.mo.momx = FixedMul(FixedMul(p.wallspeed,cos(wang)),cos(p.wallangle))
		p.mo.momy = FixedMul(FixedMul(p.wallspeed,sin(wang)),cos(p.wallangle))
		p.mo.momz = FixedMul(p.wallspeed,sin(p.wallangle))
	end
		
	// just for wallmode
	// sorry non saturn users. you'll get a better indicator next time
	if p.mo.HP_wallmode and p.mo.rollangle ~= nil then
		p.mo.rollangle = (p.wallangle-ANGLE_90)
	end
end

local function managewallmode(p)
	// evil wall logic movement: TWO!
	if p.mo.HP_wallmode then
		// no gravity, because fuck you
		p.mo.flags = $ | MF_NOGRAVITY
		// allow usage of speed items
		funcs.handleItems(p, p.mo)
		// eartboud
		p.wallspeed = $ + funcs.ThreedKartMovement(p, p.cmd)
		// apply friction regardless!!!!!!!!
		p.wallspeed = FixedMul($, p.mo.friction)
		// fuck you
		p.mo.angle = p.mo.restrictedangle
	end
end

/*
basic function that checks collision with half pipe lines
and calculates basic information
this is a barebones basic half pipe implementation to be used by people

it uses given coordinates (not z)
a range to look for lines
the type of collision (distance from line point, points in different side of the line)
lines to ignore, can be one single line, a table with lines, ro a table with other tables that has line on it (just in case)
the obejct subjected to half pipe
this calculates the difference betwen the wall and object moving angle
it normalized betwen 1, and RRACUNIT to make usage easier
it also does calculates if the obejct is moving agains or alongside the line
to decide if it should half pipe or not
mappers can check line flags to change behaviour
*/
local function localchecklinecollision(x, y, range, collisiontype, ignore , mobj, inair)
	local test = funcs.gethalfpipeline(x, y, collisiontype, range, ignore )
	local point = {}
	if not test then 
		//try a higher range
		test = funcs.gethalfpipeline(x, y, collisiontype, range + FixedMul(32*FRACUNIT, mapobjectscale), ignore )
	end
	
	//if doesnt find naything still, F
	if not test then return nil end
	
	//will be used to place the object near the half pipe line
	point = funcs.pointonline(x, y, test.wall) 
	local lineflag, ignoreslope, nogravity, landonceiling, wallmode, forcedetach, ignoreangle, forceangle
	lineflag = test.wall.flags
	
	//ah so this is the smart way to do this, LOL - Lighto (improved by Haya, A LOT)
	
	ignoreslope = (lineflag & ML_EFFECT1) // Slope Skew
	nogravity = (lineflag & ML_EFFECT2) // No Midtexture Skew 
	landonceiling = (lineflag & ML_EFFECT3) // Peg Midtexture
	wallmode = (lineflag & ML_EFFECT4) or (lineflag & ML_NOSONIC) // Solid Midtexture
	forcedetach = (lineflag & ML_EFFECT5) // Repeat Midtexture 
	ignoreangle = (lineflag & 8192) // No Knuckles
	forceangle = (lineflag & 4096) // No Tails
		
	local mospeed = FixedHypot(mobj.momx, mobj.momy) + FRACUNIT
	local momoveangle = R_PointToAngle2(0, 0, mobj.momx, mobj.momy)	
	local angdiff = abs(funcs.angletoint(momoveangle) - funcs.angletoint(test.back)) % 360
	if angdiff > 180 then
		angdiff = 360 - $
	end
	if angdiff == 0 then angdiff = 1 end //avoiding errors here
	
	//those doesnt count of youre in the air already
	if not inair then
		//must be on the floor
		if not ignoreslope
		and not P_IsObjectOnGround(mobj) then return nil end
		//oooooorr must be on a slope, that goes up (deoending on the gravity direction)
		if not ignoreslope
		and not mobj.standingslope then return nil end
		if not ignoreslope and mobj.standingslope and not ignoreangle then
			if P_MobjFlip(mobj) > 0 then
				if funcs.angletoint(mobj.standingslope.zangle) < 32
				or funcs.angletoint(mobj.standingslope.zangle) > 360 - 32 then return nil end
			else
				if abs(funcs.angletoint(mobj.standingslope.zangle) - 180) < 32 then return nil end
			end
		end
		
		//big deny fest
		if not ignoreangle
		and angdiff > 80 then return nil end
	end
	
	//deliver the angle difference normalized, youre welcome
	//gives a value between 1 and ANG1, to be more clear
	angdiff = FixedDiv(abs(momoveangle - test.back) - 1 ,(64)*ANG1 - 1) // FixedDiv((momoveangle - test.back) - 1 , (FixedAngle(80*FRACUNIT) - 1))
	if angdiff == 0 then angdiff = 1 end //avoiding errors here
	
	return {
		test,
		point,
		angdiff,
		nogravity,
		landonceiling,
		wallmode,
		forcedetach,
		forceangle,
		ignoreslope,
		test.wall.frontsector
	}	
end

/*
function that launches the object for the half pipe (emphasys on object, not player specificaly)
from here theres no return point, everything is calculated before by another function
sets some of the objects fields to keep track of behaviours and its state
*/
local function locallaunchtohalfpipe(mo, data)

	local line, angdif, point, hspeed, hspdcap, vspdcap, vspeed, momoveang = data[1], data[3], data[2], FixedHypot(mo.momx, mo.momy), data[1].wall.frontside.textureoffset , data[1].wall.frontside.rowoffset, 0,R_PointToAngle2(0, 0, mo.momx, mo.momy)
	if not hspdcap then hspdcap = hspeed end
	if not vspdcap then vspdcap = hspeed end
	//this diminishes or increases the value by percentage
	//which means you can control by the line back side texture offsets 
	//the ammount of speed you wantto be acounted
	//example:
	//100 will be the common regular speed
	//50 would be half speed (this obejct must be move faster to go higher/farther)
	//200 would be the double of speed, so it would give stronger launch with less speed
	if line.wall and line.wall.backside then
		hspeed = ($ * ((line.wall.backside.textureoffset/FRACUNIT) or 100)/100) //dont judge me
	end
	
	//cap speeds to a maximum
	hspeed = min($, hspdcap)
	vspeed =  min(hspeed, vspdcap)
	
	mo.HP_movedir = -funcs.sign(momoveang - line.back)
	mo.HP_horispeed = FixedMul(hspeed , angdif) * mo.HP_movedir
	mo.HP_vertspeed = (8*FRACUNIT) + FixedMul(vspeed ,(2048+ FRACUNIT) - (angdif - (angdif/6)) ) * P_MobjFlip(mo)
		
	//smol hack to work around slope physics
	mo.flags = $ | MF_NOCLIPHEIGHT
	mo.restrictedangle = mo.angle
	point.x = $ + FixedMul(mo.radius + 9*FRACUNIT, cos(line.front))
	point.y = $ + FixedMul(mo.radius + 9*FRACUNIT, sin(line.front))
	P_TryMove(mo, point.x, point.y, true)
	
	mo.flags = $ & ~MF_NOCLIPHEIGHT
	
	local floor
	
	if P_MobjFlip(mo) > 0 then floor = mo.floorz else floor = mo.ceilingz end
	if data[9] then
		mo.z = $ + (-P_GetMobjGravity(mo)*2) //move off the ground like its been 2 frames in game
	else
		mo.z = floor + (-P_GetMobjGravity(mo)*2) //move off the ground like its been 2 frames in game
	end
	
	P_InstaThrust(mo, line.angle, mo.HP_horispeed)
	mo.momz = mo.HP_vertspeed
	
	mo.HP_nogravity = data[4]
	mo.HP_landceiling = data[5]
	mo.HP_detach = data[7]
	mo.HP_wallmode = data[6]
	mo.HP_curline = line
	mo.HP_prevline = line
	mo.HP_forceangle = data[8]
	
	if mo.HP_wallmode and (mo.player and mo.player.valid) then
		//update your speed rn
		mo.player.wallspeed = mo.player.speed
		// is it really this fucking simple???? damn then
		mo.player.wallangle = mo.angle - line.angle
		P_InstaThrust(mo, line.angle, 0)
	end
	
	
	mo.HP_launched = true
	mo.HP_launchcd = 3
	
	if mo.HP_forceangle then mo.angle = data[1].angle end
	
	if mo.HP_nogravity then
		mo.flags = $ | MF_NOGRAVITY
	end

end

/*
functio nthat resets everything related to half pipe to be ready for the next time
*/

local function localhalfpipedeatch(mo)
	if mo.HP_wallmode and (mo.player and mo.player.valid) and P_IsObjectOnGround(mo) then
		P_InstaThrust(mo, mo.player.wallangle-ANGLE_90, 32*FRACUNIT)
		//print("hellyeah :)")
	end
	if mo.HP_nogravity or mo.HP_wallmode then 
		mo.flags = $^^MF_NOGRAVITY
		if mo.rollangle ~= nil then
			mo.rollangle = 0
		end
	end
	mo.HP_launched = false
	mo.HP_curline = nil
	mo.HP_prevline = nil
	mo.HP_wallmode = false
	mo.HP_nogravity = false
	mo.HP_landceiling = false
	mo.HP_detach = false
	mo.HP_zmom = 0
	mo.HP_launchcd = 3
	mo.HP_landcd = 3
	mo.HP_forceangle = false
	mo.HP_dropdown = false
end

/*
function to switch betwen half pipe lines when launched by one already
it wont switch for the same line or the previous
it als owont switch if not close enough
it does not account for angle difference, neither detaches if no line is found
*/
local function localswitchhalfpipeline(mo, data)
	
	local line, angdif, point, hspeed, hspdcap, vspdcap, vspeed, momoveang = data[1], data[3], data[2], FixedHypot(mo.momx, mo.momy), data[1].wall.frontside.textureoffset , data[1].wall.frontside.rowoffset, 0,R_PointToAngle2(0, 0, mo.momx, mo.momy)
	if not hspdcap then hspdcap = hspeed end

	hspeed = min($, hspdcap)

	mo.HP_movedir = -funcs.sign(momoveang - line.back)
	mo.HP_horispeed = hspeed * mo.HP_movedir
	
	P_InstaThrust(mo, line.angle, mo.HP_horispeed)
	
	mo.flags = $ | MF_NOCLIPHEIGHT
	
	point.x = $ + FixedMul(mo.radius + 9*FRACUNIT, cos(line.front))
	point.y = $ + FixedMul(mo.radius + 9*FRACUNIT, sin(line.front))
	P_TryMove(mo, point.x, point.y, true)
	
	mo.flags = $ & ~MF_NOCLIPHEIGHT

	mo.HP_nogravity = data[4]
	mo.HP_landceiling = data[5]
	mo.HP_detach = data[7]
	mo.HP_forceangle = data[8]
	
	mo.HP_prevline = mo.HP_curline
	mo.HP_curline = line
	
	if mo.HP_forceangle then mo.angle = data[1].angle end
	
	if mo.HP_nogravity then
		mo.flags = $ | MF_NOGRAVITY
	end

end

/*
allows to set/change some effects related to half pipes
use more liendef flags for more variety of effects
*/
addHook("LinedefExecute", function( line,  mobj,  sector)
	if not (mobj and mobj.valid) then return end
	
	if line.flags & ML_EFFECT2 
	and mo.HP_launched then //No Midtexture Skew 
		//YKNOW theres the vanilla trigger. but the var
		mobj.HP_nogravity = true
		mobj.flags = $^^MF_NOGRAVITY
	end
	
	if line.flags & ML_EFFECT3 then //Peg Midtexture
		mobj.HP_landceiling = true
	end
	
	if line.flags & ML_EFFECT4 then //Solid Midtexture
		mobj.HP_wallmode = true
		//call wall mode functions here
	end
	
	if line.flags & ML_EFFECT5 then //Repeat Midtexture 
		mobj.HP_detach = true
	end 

	//set delay (in tics) to wait for to be launched again
	if line.frontside.textureoffset ~= 0 then
		mobj.HP_launchcd = abs(line.frontside.textureoffset)/FRACUNIT
	end
	
	//give a thrusto to the object if in half pipe mode
	if mobj.HP_launched then
		//owith this flag affects horizontal speed
		if line.frontside.rowoffset ~= 0
		and line.flags & 8 then //upper unpegged
			//invert current speed
			if line.frontside.rowoffset == FRACUNIT then
				P_Thrust(mobj, mo.HP_curline.angle, FixedHypot(mobj.momx, mobj.momy)*-1)
			else
				//set specific speed
				P_Thrust(mobj, mo.HP_curline.angle, line.frontside.rowoffset)
			end
			
		end
		
		//with this flag affects vertical speed
		if line.frontside.rowoffset ~= 0
		and line.flags & 16 then //lower unpegged
			if line.frontside.rowoffset == FRACUNIT then
				//invert speed
				mobj.momz = $ *-1
			else
				//set specific speed
				mobj.momz = $ + line.frontside.rowoffset
			end
			
		end
	end
end, "HALFPIPE")


local molist = {MT_PLAYER, MT_ORBINAUT, MT_JAWZ}

for i = 1, #molist do
addHook("MobjThinker",function(mo)
	if not (HalfPipe and HalfPipe.proceed) then return end //F
	funcs = HalfPipe.funcs
	if not (mo and mo.valid) then return end
	if not mo.HP_init then
		mo.HP_init = true
		mo.HP_launched = false //let you know if your got launched into a halfpipe
		mo.HP_curline = nil //current halfpipe line
		mo.HP_prevline = nil //previous half pipel ine to prevent bugs also let do some calcs
		mo.HP_zmom = 0 //keeps your vertical momentum while in the half pipe, to use when landing
		mo.HP_landcd = 0 //note: unused  //waits this delay counter to reach 0 to let you get launched again
		mo.HP_launchcd = 0 //wait for some frames to launch you up, tring interopability here
		mo.HP_vertspeed = 0
		mo.HP_horispeed = 0
		mo.HP_movedir = 0 //which direction we will move (-1 or 1)
		mo.HP_wallmode = false
		mo.HP_nogravity = false
		mo.HP_landceiling = false
		mo.HP_detach = false
		mo.HP_forceangle = false
		mo.HP_dropdown = false
	end
	local speed = FixedHypot(mo.momx, mo.momy)
	local search = localchecklinecollision(mo.x, mo.y, FixedMul(speed + mo.radius + (16*FRACUNIT), mapobjectscale), 1, {mo.HP_curline, mo.HP_prevline} , mo, mo.HP_launched)

	
	//land on the ceiling
	if  mo.HP_launched and ((search and search[5]) or mo.HP_landceiling)
	and ((P_MobjFlip(mo) > 0 and mo.z + mo.height >= mo.ceilingz)
	or(P_MobjFlip(mo) < 0 and mo.z <= mo.floorz ) ) then	 
		mo.flags2 = $^^MF2_OBJECTFLIP
		mo.eflags = $^^MFE_VERTICALFLIP
		if mo.HP_wallmode and (mo.player and mo.player.valid) then
			mo.flags = $^^MF_NOGRAVITY
			// just for wallmode
			// sorry non saturn users. you'll get a better indicator next time
			if mo.rollangle ~= nil then
				mo.rollangle = 0
			end
			//print("hellyeah :)")
		end
	end
	
	//land
	//forcefully detach
	if (P_IsObjectOnGround(mo) and mo.HP_launched and mo.HP_launchcd <= 0)
	or ((search and search[7]) or mo.HP_detach)
	or mo.HP_wallmode and (mo.HP_curline and mo.z > mo.HP_curline.wall.backsector.floorheight)
	or i == 1 and (mo.HP_curline and mo.player and mo.player.kartstuff[k_spinouttimer])then
		//normal landing
		//change angle (looking direction)
		//add some speed when landing on slopes
		if P_IsObjectOnGround(mo) and not ((search and search[7]) or mo.HP_detach) and not mo.HP_wallmode then
			if not search then //youre in the same line you got launched
				if abs(mo.HP_horispeed) < 2*FRACUNIT then					
					//reverse 180 since you didnt move horizontally
					local ang = mo.HP_curline.front
					//print(FixedInt(AngleFixed(mo.angle-ang)))
					mo.wallthrustangle = ang
				else
					local ang = mo.HP_curline.front - ((84*ANG1) * -mo.HP_movedir)
					//adjust your angle to the wall so the camera displays were you going
					//print(FixedInt(AngleFixed(mo.angle-ang)))
					//if mo.angle-ang < ang then
						mo.wallthrustangle = ang
					//end
				end
				//in this scenario what we want is to revert the abgle
				//or else the object wil ldeccelerate
				if mo.HP_landceiling then
					//"loops"
					mo.wallthrustangle = mo.HP_curline.front
				elseif (search and search[5]) then
					mo.wallthrustangle = search[1].front
				end
			end
			if mo.HP_forceangle then
				mo.angle = mo.wallthrustangle
			end
			if mo.standingslope and mo.wallthrustangle then
				// alleyoop
				mo.friction = abs(mo.HP_zmom)/2
				mo.frictiontimer = TICRATE
				P_Thrust(mo, mo.wallthrustangle, abs(mo.HP_zmom)/2)
			end
		end
		//detach
		//if a line is marked to detach or yo uset via linedef trigger
		if ((search and search[7]) or mo.HP_detach) and mo.HP_launched and not mo.HP_wallmode and mo.HP_forceangle then
			mo.angle = mo.HP_curline.front - ((84*ANG1) * -mo.HP_movedir)
			
		end
		//unset some stuff
		localhalfpipedeatch(mo)
		
	end
	
	if mo.frictiontimer and mo.frictiontimer > 0 then
		mo.friction = max(ORIG_FRICTION, FRACUNIT*(mo.frictiontimer/18))
		mo.frictiontimer = $ - 1
		if mo.frictiontimer == 0 then
			mo.friction = ORIG_FRICTION
		end
	end
	
	//launch
	if search then
		//common first launch
		if mo.HP_launchcd <= 0 
		and mo.HP_launched == false 
		and (P_IsObjectOnGround(mo) or not search[9])
		and not (search[7] or mo.HP_detach)
		and not (i == 1 and (mo.player and mo.player.kartstuff[k_spinouttimer])) then
			//proceed normally to launch to half pipe
			locallaunchtohalfpipe(mo, search)	
		end
		
		//switch to another half pipe line instead
		if mo.HP_launched
		and FixedHypot(
		mo.x - (search[2].x + FixedMul(mo.radius + (9*FRACUNIT),cos(search[1].front))),
		mo.y - (search[2].y + FixedMul(mo.radius + (9*FRACUNIT),sin(search[1].front))) ) < (9*FRACUNIT)+(speed + FRACUNIT) then
			//checks if youre close enough to switch to the enxt line
			//so the obejct does not bonk on obstacles for trying to seitch too early
			localswitchhalfpipeline(mo, search)
		end

	end
	
	//manage wall mode
	//that was started by me but totally finished by Haya, he did a lot more on it and its awesome
	
	if (mo.player and mo.player.valid) and mo.HP_wallmode then
		managewallmode(mo.player)
	end
	
	if mo.HP_launched then
		//adjust camera by chaning object angle (the player)
		if mo.HP_horispeed ~= 0 and i == 1 and not mo.HP_wallmode and mo.HP_forceangle then
			mo.angle = mo.HP_curline.back + (ANGLE_45 * -mo.HP_movedir)
		end

		//store's player speed mdiair to use when landing
		mo.HP_zmom = mo.momz
		if mo.HP_nogravity then
			mo.HP_zmom = mo.HP_vertspeed
		end
		
		if i == 1 and mo.player and not mo.HP_wallmode then
			if mo.player.cmd.buttons & BT_BRAKE and not mo.HP_dropdown and ((P_MobjFlip(mo) > 0 and mo.momz > 0) or (P_MobjFlip(mo) < 0 and mo.momz < 0)) then
				mo.HP_dropdown = true
			end
			if mo.HP_dropdown then
				mo.momz = $ - (5*gravity)/3 * P_MobjFlip(mo)
			end
		end
	end
	mo.HP_launchcd = max(0, $ - 1)


end,molist[i])
end

addHook("PostThinkFrame", function()
	for p in players.iterate do
		if not (p.mo and p.mo.valid) then continue end
		managewallmode_post(p)
	end
end)